//归并排序
//归并排序的基本思想可以分成两部分：将待排序的数组拆分成有序数组、将两个有序的列表合并成一个有序的列表
//https://leetcode-cn.com/leetbook/read/sort-algorithms/euivj1/

//不存在原地归并排序，即一定会开辟额外的空间

//时间复杂度O(nlogn)，空间复杂度O(n)。主要占用空间的就是长度为n的result数组。

//是一种稳定的排序算法

import java.util.Arrays;

public class mergeSort {

    public static void main(String[] args) {
        int[] arr = new int[]{6,1,2,3,5,4};
        mergeSort(arr);
        System.out.println(Arrays.toString(arr));
    }

    //归并排序入口
    public static void mergeSort(int[] arr) {
        if (arr.length == 0){
            return ;
        }

        int[] result = new int[arr.length];
        // result数组存储排序合并的结果，并且在递归过程中临时存储左右区间，节省空间
        // 如果不采用这种方式，更容易的理解是每次递归都要开辟新的临时数组left和right来存储，可以参考mergeSort_test工程

        mergeSort(arr, 0, arr.length - 1, result);
    }

    // 方法重载
    // 对 arr 的 [start, end] 区间归并排序
    private static void mergeSort(int[] arr, int start, int end, int[] result) {
        // 只剩下一个数字，停止拆分
        if (start == end) return;

        int middle = (start + end) / 2;
        // 拆分左边区域，并将归并排序的结果保存到 result 的 [start, middle] 区间
        mergeSort(arr, start, middle, result);
        // 拆分右边区域，并将归并排序的结果保存到 result 的 [middle + 1, end] 区间
        mergeSort(arr, middle + 1, end, result);
        // 合并左右区域到 result 的 [start, end] 区间
        merge(arr, start, end, result);
    }

    // 将 result 的 [start, middle] 和 [middle + 1, end] 区间合并
    private static void merge(int[] arr, int start,  int end, int[] result) {
        int middle = (start + end) / 2;
        // 数组 1 的首尾位置
        int start1 = start;
        int end1 = middle;
        // 数组 2 的首尾位置
        int start2 = middle + 1;
        int end2 = end;
        // 用来遍历数组的指针
        int index1 = start1;
        int index2 = start2;
        // 结果数组的指针
        int resultIndex = start1;
        while (index1 <= end1 && index2 <= end2) {
            //哪个小，哪个就先放进结果数组里，保证升序排列，最后剩下的直接放进去
            if (arr[index1] <= arr[index2]) {
                result[resultIndex++] = arr[index1++];
            } else {
                result[resultIndex++] = arr[index2++];
            }
        }
        // 将剩余数字补到结果数组之后
        while (index1 <= end1) {
            result[resultIndex++] = arr[index1++];
        }
        while (index2 <= end2) {
            result[resultIndex++] = arr[index2++];
        }
        // 将 result 操作区间的数字拷贝到 arr 数组中，以便下次比较
        // arr直接被改变
        for (int i = start; i <= end; i++) {
            arr[i] = result[i];
        }
    }
}
